<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_document_baseDocument
{
	private $document_author;	
	private $document_creationdate;
	private $document_modificationdate;
	private $document_startpublicationdate;
	private $document_endpublicationdate;
	private $document_label;	
	private $document_VO;
	private $document_lang;	
	private $document_publicationstatus;	
	private $document_model;
	private $document_revision;
	private $document_uid;
	private $document_id;
	
	const NOTRANSLATION = "NOT TRANSLATED";

	/**
	 * Constructor
	 *
	 * @param integer $id
	 * @return f_document_baseDocument
	 */
	public function __construct($id = null, $revision = null, $lang = null)
	{		
		if(!is_null($id))
		{
			$this->loadDocument($id, $revision, $lang);
		}
		else
		{
			$this->setLang($this->getContext()->getLang());			
			$this->setPublicationstatus($this->getPublicationstatus());
			$this->setModel($this->getDocumentModel());
		}

		return $this;
	}
	
	public function __toString()
	{
		return $this->getUniqueId()." / ".$this->getLang()." / ".$this->getLabel();
	}
	
	/**
	 * Load document datas
	 *
	 * @param integer $id
	 * @param integer $revision
	 */
	protected function loadDocument($id, $revision = null, $lang = null)
	{
		$dbProvider = f_database_Provider::getInstance();

		if(is_null($lang))
		{
			$lang = $this->getContext()->getLang();
		}
		
		$lang = strtoupper($lang);
		
		$exception = "";

		$tableName = $this->getTableName();

		$attributes = array();
		
		if(is_string($revision)) $revision = intval($revision);

		// ================= NON LOCALIZED
		// Get the head revision from non localized document
		if(!$this->isLocalized())
		{				
			if(is_null($revision))
			{
				$attributes = $dbProvider->getDatasById($tableName, $id);
				$exception = sprintf("Cannot load document id(%s) from table(%s)", $id, $tableName);				
			}
			// Get the specified revision from the original language
			elseif(is_int($revision) && $this->isRevisable())
			{
				$attributes = $dbProvider->getRevisionDatasById($tableName, $id, $revision);
				$exception = sprintf("Cannot load document id(%s) revision(%s) from table(%s)", $id, $revision, $tableName);	
			}
			$this->document_VO = $attributes[0]["document_lang"];				
		}
		// ================= LOCALIZED			
		else
		{				
			$this->getVOLang($id);
			
			if(is_null($this->document_VO))
			{
				$this->document_VO = $lang;
			}
		
			// Get the head revision from given language
			if(is_null($revision))
			{						
				if($this->document_VO != $lang)
				{			
					// load localized document if exist...							
					$attributes = $dbProvider->getLocalizedDatasById($tableName, $id, $lang);

					// else create new localization document
					if(end($attributes) === false)
					{								
						$attributes = $dbProvider->getDatasById($tableName, $id);

						foreach($this->localizedFields() as $field)
						{
                            if($field != 'document_id')
                            {
                                $attributes[0][$field] = null;
                            }
						}
						$attributes[0]['document_uid'] = $id;
						$attributes[0]['document_lang'] = $lang;
						$attributes[0]['document_publicationstatus'] = self::NOTRANSLATION;
					}
																	
				}
				else
				{
					$attributes = $dbProvider->getDatasById($tableName, $id);
				}					
				
				$exception = sprintf("Cannot load localized(%s) document id(%s) from table(%s)", $lang, $id, $tableName."_i18n");
			}
			// Get the specified revision from given language
			elseif($this->isRevisable())
			{											
				if($this->document_VO != $lang)
				{		
					$attributes = $dbProvider->getDatasByRevisionLangAndId($tableName, $revision, $id, $lang);
					$exception = sprintf("Cannot load localized(%s) document id(%s) from table(%s)", $lang, $id, $tableName."_revisions_i18n");
				}
				else
				{
					$attributes = $dbProvider->getRevisionDatasById($tableName, $id, $revision);
					$exception = sprintf("Cannot load document id(%s) revision(%s) from table(%s)", $id, $revision, $tableName."_revisions");
				}
			}			
		}

		$attributes = end($attributes);

		if($attributes === false)
		{	
			throw new Exception($exception);
		}

		foreach ($attributes as $field => $value)
		{	
			$method = str_replace("document_", "", $field);
			
			$methodName = "set".ucfirst($method);	
			
			if(method_exists($this, $methodName))
			{				
				$this->$methodName($value);
			}
			else
			{
				$this->$field = $value;
			}
		}
	}

	/**
	 * Get document module name
	 * @return string
	 */
	public function getDocumentModuleName()
	{
		list($module, $type) = explode("/", substr($this->getModel(), 8));
		return $module;
	}
	
	/**
	 * Get document type name
	 * @return string
	 */
	public function getDocumentTypeName()
	{
		$md = substr($this->getModel(), 8);

		list($module, $type) = explode("/", $md);
		return $type;
	}
	
	/**
	 * Get the original language
	 *
	 * @param integer $id
	 */
	private function getVoLang($id)
	{		
		$dbProvider = f_database_Provider::getInstance();	
		
		$sql = "SELECT `document_lang` FROM `".$this->getTableName()."` WHERE `document_uid`=:id";
		
		$lang = $dbProvider->setQuery($sql)->execute(array("id" => $id))->getQueryResult(PDO::FETCH_COLUMN);

		$this->document_VO = $lang;
	}
	
	/**
	 * Get the setted VO lang
	 * 
	 * @return string
	 */
	public function getVO()
	{
		return $this->document_VO;
	}
	
	/**
	 * @return f_core_Context
	 */
	protected function getContext()
	{
		return f_core_Context::getInstance();
	}

	/**
	 * @param integer $value
	 * @return f_document_baseDocument
	 */
	private function setUniqueId($value)
	{
		$this->document_uid = $value;
		return $this;
	}
	
	/**
	 * Is current document already translated
	 * 
	 * @return boolen
	 */
	public function isTranslated()
	{
		return $this->getPublicationstatus() != self::NOTRANSLATION;
	}

    public function isPublished()
    {
        return $this->getPublicationstatus() == 'PUBLISHED';
    }

	/**
	 * @return integer
	 */
	public function getId()
	{
		return $this->document_id;
	}

	/**
	 * @return integer
	 */
	public function setId($value)
	{
		$this->document_id = $value;
        return $this;
	}

	/**
	 * @return integer
	 */
	public function getUniqueId()
	{
		return $this->document_uid;
	}

	/**
	 * @param string $value
	 * @return f_document_baseDocument
	 */
	public function setModel($value)
	{
		$this->document_model = $value;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getModel()
	{
		return $this->document_model;
	}

	/**
	 * @param string $value
	 * @return f_document_baseDocument
	 */
	public function setLabel($value)
	{
		$this->document_label = $value;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getLabel()
	{
		return $this->document_label;
	}

	/**
	 * @param string $value
	 * @return f_document_baseDocument
	 */
	public function setAuthor($value)
	{
		$this->document_author = $value;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getAuthor()
	{
		return $this->document_author;
	}

	/**
	 * @param date $value
	 * @return f_document_baseDocument
	 */
	public function setCreationDate($value)
	{
		$this->document_creationdate = $value;
		return $this;
	}

	/**
	 * @return date
	 */
	public function getCreationDate($formatted = false, $lang = null, $smartFormat = "localized-user-time-format")
	{
		if($formatted === false)
		{
		    return $this->document_creationdate;
		}
		else
		{
		    return f_date_DateFormat::smartFormatByLang($this->document_creationdate, $smartFormat, $lang);
		}
	}

	/**
	 * @param date $value
	 * @return f_document_baseDocument
	 */
	public function setModificationDate($value)
	{
		$this->document_modificationdate = $value;
		return $this;
	}

	/**
	 * @return date
	 */
	public function getModificationDate($formatted = false, $lang = null, $smartFormat = "localized-user-time-format")
	{
		if($formatted === false)
		{
		    return $this->document_modificationdate;
		}
		else
		{
		    return f_date_DateFormat::smartFormatByLang($this->document_modificationdate, $smartFormat, $lang);
		}
	}
	
	/**
	 * @param integer $value
	 * @return f_document_baseDocument
	 */
	public function setRevision($value)
	{
		$this->document_revision = $value;
		return $this;
	}
	
	/**
	 * @return integer
	 */
	public function getDocRevision()
	{
		return $this->document_revision;
	}
	
	/**
	 * @return integer
	 */
	public function getLastRevisionNumber()
	{
		$dbProvider = f_database_Provider::getInstance();
		
		if($this->isLocalized())
		{
			if($this->isATranslation())
			{
				return $dbProvider->getLastRevisionId($this->getTableName()."_i18n", $this->getUniqueId(), $this->getLang());
			}
		}		
		return $dbProvider->getLastRevisionId($this->getTableName(), $this->getUniqueId(), $this->getLang());
	}
	
	/**
	 * @return string
	 */
	public function getPublicationstatus()
	{
		if(is_null($this->document_publicationstatus)) return $this->getdefaultPublicationstatus();
		return $this->document_publicationstatus;
	}

	/**
	 * @param string $value
	 * @return f_document_baseDocument
	 */
	public function setPublicationstatus($value)
	{
		$this->document_publicationstatus = $value;
		return $this;
	}

	/**
	 * @param date $value
	 * @return f_document_baseDocument
	 */
	public function setStartpublicationdate($value)
	{
		$this->document_startpublicationdate = $value;
		return $this;
	}

	/**
	 * @return date
	 */
	public function getStartpublicationdate($formatted = false, $lang = null, $smartFormat = "localized-user-time-format")
	{
		if($formatted === false)
		{
		    return $this->document_startpublicationdate;
		}
		else
		{
		    if(!is_null($this->document_startpublicationdate))
		    {
			return f_date_DateFormat::smartFormatByLang($this->document_startpublicationdate, $smartFormat, $lang);
		    }
		    else
		    {
			return "";
		    }
		}
	}

	/**
	 * @param date $value
	 * @return f_document_baseDocument
	 */
	public function setEndpublicationdate($value)
	{
		$this->document_endpublicationdate = $value;
		return $this;
	}

	/**
	 * @return date
	 */
	public function getEndpublicationdate($formatted = false, $lang = null, $smartFormat = "localized-user-time-format")
	{
		if($formatted === false)
		{
		    return $this->document_endpublicationdate;
		}
		else
		{
		    if(!is_null($this->document_endpublicationdate))
		    {
			return f_date_DateFormat::smartFormatByLang($this->document_endpublicationdate, $smartFormat, $lang);
		    }
		    else
		    {
			return "";
		    }
		}
	}
	
	/**
	 * Is current date/time between start and end publication dates
	 *
	 * @return boolean
	 */
	public function isPublicationDatesValid()
	{
		if(is_null($this->document_startpublicationdate) || strtotime($this->document_startpublicationdate) <= strtotime("now"))
		{
			if(is_null($this->document_endpublicationdate) || strtotime($this->document_endpublicationdate) >= strtotime("now"))
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * @param string $value
	 * @return f_document_baseDocument
	 */
	public function setLang($value)
	{
		$this->document_lang = strtoupper($value);
		return $this;
	}

	/**
	 * @return string
	 */
	public function getLang()
	{
		return $this->document_lang;
	}
	
	public function relationFields()
	{
		return array();
	}
	
// ==================================================== SAVING METHOD	

	/**
	 * Clone current document to a new one with a different lang
	 * @return object
	 * @param string $lang
	 */
	public function cloneToLang($lang)
	{
		$clone = clone $this;
		$clone->setLang($lang);
		return $clone;
	}
	
	/**
	 * Clone current document to a new one
	 * @return object
	 */	
	public function cloneToNew()
	{
		$clone = clone $this;
		$clone->setUniqueId(null);
		return $clone;
	}

	/**
	 * Clone current document (for temp per example)
	 * @return object
	 */
	public function cloneDocument()
	{
		return clone $this;
	}

	/**
	 * Save or update the document
	 */
	public function save($saveRevision = true)
	{
        $dbProvider = f_database_Provider::getInstance();

        try
		{
			$dbProvider->beginTransaction();

            if(method_exists($this, "preSave"))
            {
                $preSaveStatus = $this->preSave();

                if($preSaveStatus === false)
                {
                    $dbProvider->rollBack();
                    return false;
                }
            }

            if(is_null($this->document_VO))
            {
                $this->document_VO = $this->getLang();
            }

            // Saving new document (id=null)
            if(is_null($this->getUniqueId()))
            {
                $this->saveNewDocument($saveRevision);
            }
            // Or update
            else
            {
                // document is not localized
                if(!$this->isLocalized())
                {
                    //simply update
                    $this->updateDocument($saveRevision);
                }
                else
                {
                    //If document updated has the same lang as original document
                    if($this->document_VO == $this->getLang())
                    {
                        //simply update
                        $this->updateDocument($saveRevision);
                    }
                    //else save in localized table
                    else
                    {
                        if(!$this->isI18nDocumentInTable())
                        {
                            $this->saveI18nDocument($saveRevision);
                        }
                        else
                        {
                            $this->updateI18nDocument($saveRevision);
                        }
                    }
                }
            }

            if(method_exists($this, "postSave"))
            {
                $postSaveStatus = $this->postSave();

                if($postSaveStatus === false)
                {
                    $dbProvider->rollBack();
                    return false;
                }
            }
            
            $dbProvider->commit();
		}
		catch (PDOException  $e)
		{
			$dbProvider->rollBack($e);
		}
		
		return $this;
	}
	
// ==================================================== SAVING METHODS / NON LOCALIZED DOCUMENTS	
	
	/**
	 * Save a new document in database
	 */
	private function saveNewDocument($saveRevision)
	{
		$dbProvider = f_database_Provider::getInstance();
		$relationManager = f_relation_Manager::getInstance();
		$cache = f_core_Cache::getInstance();
		
        $this->setCreationDate(date("Y-m-d H:i:s"));
        $this->setModificationDate(date("Y-m-d H:i:s"));

        $dbProvider->insert("f_document", array("document_uid" => 0, "document_model" => $this->getModel()));

        $this->setUniqueId($this->getLastId());

        if($this->isRevisable() && $saveRevision == true)
        {
            $this->setRevision(0);
            $dbProvider->insert($this->getTableName()."_revisions", $this->getDocumentProperties());
        }

        $dbProvider->insert($this->getTableName(), $this->getDocumentProperties());

        // Update relations
        $this->defineRelations();

		if($this->isRevisable() && $saveRevision == true)
		{
			$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang(), 0), $this, -1);
		}
		
		$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()), $this);
	}
	
	/**
	 * Update a document in database
	 */
	private function updateDocument($saveRevision)
	{
		$this->setModificationDate(date("Y-m-d H:i:s"));
		$dbProvider = f_database_Provider::getInstance();
		$relationManager = f_relation_Manager::getInstance();
		$cache = f_core_Cache::getInstance();

        // Add a new revision if document is revisable
        if($this->isRevisable() && $saveRevision == true)
        {
            $revisionNumber = $this->getLastRevisionNumber()+1;
            $this->setRevision($revisionNumber);
            $res = $dbProvider->insert($this->getTableName()."_revisions", $this->getDocumentProperties());
        }

        // Update fields
        $dbProvider->update($this->getTableName(), $this->getUniqueId(), $this->getDocumentProperties());

        // Update relations
        $this->defineRelations();
		
		if($this->isRevisable() && $saveRevision == true)
		{
			$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang(), $revisionNumber), $this, -1);
		}
		
		$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()), $this);
	}
	
// ==================================================== SAVING METHODS / LOCALIZED DOCUMENTS	
	
	/**
	 * Saving a new localized document
	 */
	private function saveI18nDocument($saveRevision)
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
		
		// Setting dates
		$this->setModificationDate(date("Y-m-d H:i:s"));		
		$this->setCreationDate(date("Y-m-d H:i:s"));
		
		if($this->getPublicationstatus() == self::NOTRANSLATION)
		{
			$this->setPublicationstatus($this->getdefaultPublicationstatus());
		}

		// Init revision number
		if($this->isRevisable())
		{				
			$this->setRevision(0);
		}
		
		// Get values for i18n table
		$i18nProperties = $this->localizedFields();
		$validProperties = array();
		
		foreach($this->getDocumentProperties() as $key => $property)
		{		
			foreach($i18nProperties as $prop)
			{
				if(stristr($key, $prop) !== false)
				{
					$validProperties[$key] = $property;
				}
			}
		}	
			
        // Insert revision if needed
        if($this->isRevisable() && $saveRevision == true)
        {
            $dbProvider->insert($this->getTableName()."_revisions_i18n", $this->getDocumentProperties());
        }

        $dbProvider->insert($this->getTableName()."_i18n", $validProperties);

        // Update relations
        $this->defineRelations();

		if($this->isRevisable() && $saveRevision == true)
		{	
			$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang(), 0), $this, -1);
		}
		
		$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()), $this);
	}
	
	/**
	 * Update i18n document
	 */
	private function updateI18nDocument($saveRevision)
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
		
		$this->setModificationDate(date("Y-m-d H:i:s"));
		
		$i18nProperties = $this->localizedFields();
		$validProperties = array();

		foreach($this->getDocumentProperties() as $key => $property)
		{		
			foreach($i18nProperties as $prop)
			{
				if(stristr($key, $prop) !== false)
				{
					$validProperties[$key] = $property;
				}
			}
		}

		if($this->isRevisable() && $saveRevision == true)
		{				
			$revisionNumber = intval($this->getLastRevisionNumber())+1;
			$this->setRevision($revisionNumber);
			$validProperties['document_revision'] = $revisionNumber;
		}			
			
        if($this->isRevisable() && $saveRevision == true)
        {
            $res = $dbProvider->insert($this->getTableName()."_revisions_i18n", $this->getDocumentProperties());
        }

        $dbProvider->updateByKey($this->getTableName()."_i18n", array('document_uid' => $this->getUniqueId(), 'document_lang' => $this->getLang()), $validProperties);

        // Update relations
        $this->defineRelations();

		if($this->isRevisable() && $saveRevision == true)
		{
			$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang(), $revisionNumber), $this, -1);
		}	
		
		$cache->putInCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()), $this);
	}
	
// =========================================== DELETING METHODS	
	
	/**
	 * Delete the document and relation
	 *
	 */	
	public function delete()
	{
        try
        {
            $dbProvider = f_database_Provider::getInstance();
            $dbProvider->beginTransaction();

            $this->deleteCurrentDocument(false);
            
            $dbProvider->commit();
		}
		catch (PDOException $e)
		{
			$dbProvider->rollBack($e);
		}
	}
	
	/**
	 * Delete current document and all childs documents
	 *
	 */
	public function deleteRecursive($lang = null)
	{
		$dbProvider = f_database_Provider::getInstance();
		try
		{	
			$dbProvider->beginTransaction();
			
			$this->deleteCurrentDocument(true, $lang);
			
			$dbProvider->commit();
		}	
		catch (PDOException $e)
		{
			$dbProvider->rollBack($e);
		}			
	}
	
	/**
	 * Delete method
	 *
	 * @param boolean $cascadeMod
	 */
	private function deleteCurrentDocument($recursive = false, $lang = null)
	{
		f_logs_Logger::getInstance()->actionLog("-> Deleting ".$this);
		
		if(method_exists($this, "preDelete"))
		{
			$this->preDelete();
		}		
		
		$cache = f_core_Cache::getInstance();

		if(is_null($lang) || $lang == "")
		{
			$lang = $this->getLang();
		}

		$this->deleteTargets();

		// document is not localized, simply delete it
		if(!$this->isLocalized())
		{
			$this->deleteChildsDocuments($recursive);
			$this->deleteOriginalDocument();
			$this->deleteUniqueId();
			$this->deleteCurrentDocumentChildRelation($lang);
		}
		// document is localizable
		else
		{			
			// current document is in original language
			if(!$this->isATranslation())
			{
				$this->deleteAllLangChildsDocuments($recursive);
				$this->deleteTranslations();
				$this->deleteOriginalDocument();
				$this->deleteUniqueId();
				$this->deleteCurrentDocumentChildRelation("*");
			}
			// document is a translation
			else
			{		
				$this->deleteChildsDocuments($recursive);					
				$this->deleteDocumentTranslation();
				$this->deleteCurrentDocumentChildRelation($lang);
			}
		}
	
		if(method_exists($this, "postDelete"))
		{
			$this->postDelete();
		}		
	}

	private function deleteTargets()
	{
	    $dbProvider = f_database_Provider::getInstance();

	    $dbProvider->delete("f_target", array("document_uid" => $this->getUniqueId(), "document_lang" => $this->getLang()));
	}
	
	/**
	 * Delete current original version document
	 */
	private function deleteOriginalDocument()
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
				
		if($this->isRevisable())
		{
			$dbProvider->delete($this->getTableName()."_revisions", array("document_uid" => $this->getUniqueId()));	
			$cache->clearAllDocumentRevisionsCache($this->getUniqueId(), $this->getLang());		
		}
		
		$dbProvider->delete($this->getTableName(), array("document_uid" => $this->getUniqueId()));
		$cache->clearCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()));
	}
	
	/**
	 * Delete current original document and all translations
	 */
	private function deleteTranslations()
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
		
		// Get all translations langs
		$sql = "SELECT `document_lang` FROM `".$this->getTableName()."_i18n` WHERE `document_uid`=:id";		
		$langs = $dbProvider->setQuery($sql)->execute(array("id" => $this->getUniqueId()))->getQueryResults(PDO::FETCH_COLUMN);
		
		if(count($langs) >= 1)
		{
			// Clear all translated document cache and revisions cache
			foreach($langs as $lang)
			{
				if($this->isRevisable())
				{
					$cache->clearAllDocumentRevisionsCache($this->getUniqueId(), $lang);		
				}		

				$cache->clearCache($cache->getDocumentCacheKey($this->getUniqueId(), $lang));			
			}
		}
		
		// Clear translated revisions from db
		if($this->isRevisable())
		{
			$dbProvider->delete($this->getTableName()."_revisions_i18n", array("document_uid" => $this->getUniqueId()));			
		}
		
		// Clear translation from db
		$dbProvider->delete($this->getTableName()."_i18n", array("document_uid" => $this->getUniqueId()));
	}
	
	/**
	 * Delete only current document translation
	 */
	private function deleteDocumentTranslation()
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
				
		if($this->isRevisable())
		{
			$dbProvider->delete($this->getTableName()."_revisions_i18n", array("document_uid" => $this->getUniqueId()));	
			$cache->clearAllDocumentRevisionsCache($this->getUniqueId(), $this->getLang());		
		}
		
		$dbProvider->delete($this->getTableName()."_i18n", array("document_uid" => $this->getUniqueId(), "document_lang" => $this->getLang()));

		$cache->clearCache($cache->getDocumentCacheKey($this->getUniqueId(), $this->getLang()));		
	}
	
	/**
	 * Delete entry for current document into f_document unique id table
	 */
	private function deleteUniqueId()
	{
		$dbProvider = f_database_Provider::getInstance();
		
		$dbProvider->delete("f_document", array("document_uid" => $this->getUniqueId()));
	}
	
	private function deleteCurrentDocumentChildRelation($lang = null)
	{
		$dbProvider = f_database_Provider::getInstance();
		$cache = f_core_Cache::getInstance();
		
		if(is_null($lang))
		{
			$lang = $this->getLang();
		}
		
		$sql = "SELECT `parent_id` FROM `f_relation` WHERE `child_id`=:docId";
		
		$ids = $dbProvider->setQuery($sql)->execute(array("docId" => $this->getUniqueId()))->getQueryResults(PDO::FETCH_COLUMN);
			
		if($lang != "*")
		{
			if(count($ids) > 0)
			{
				foreach($ids as $id)
				{
					$cache->clearCache($cache->getDocumentCacheKey($id, $lang));
					$dbProvider->delete("f_relation", array("parent_id" => $id, "child_id" => $this->getUniqueId(), "lang" => $lang));
				}
			}	
					
			$dbProvider->delete("f_relation", array("child_id" => $this->getUniqueId(), "lang" => $lang));			
		}
		else
		{
			$dbProvider->delete("f_relation", array("child_id" => $this->getUniqueId()));
		}
	}
	
	/**
	 * Delete all relations for current document
	 */
	private function deleteChildsDocuments($recursive = false)
	{
		$relationManager = f_relation_Manager::getInstance();		
				
		if(in_array("relationFields", get_class_methods($this)))
		{		
			$relationField = $this->relationFields();
			
			if($relationField != null)
			{
				$childs = array();
				
				$relationManager = f_relation_Manager::getInstance();
				
				foreach($relationField as $field)
				{
					$relations = $relationManager->getChilds($this, $field, $this->getLang());

					if($relations != null || is_array($relations) && !empty($relations[0]))
					{
						foreach($relations as $relation)
						{	
                            if($recursive)
                            {
                                $relation->deleteRecursive($this->getLang());
                                $relationManager->clearRelation($this, $relation, $field, $this->getLang());
                            }
						}
					}
				}
			}			
		}
	}
	
	private function deleteAllLangChildsDocuments($recursive = false)
	{
		$relationManager = f_relation_Manager::getInstance();
		
		if(in_array("relationFields", get_class_methods($this)))
		{		
			$relationField = $this->relationFields();
			
			if($relationField != null && count($relationField) > 0)
			{
				$childs = array();
				
				$relationManager = f_relation_Manager::getInstance();
				
				foreach($relationField as $field)
				{
					$langs = $this->getAllRelationsLangs($field);

					if(count($langs) > 0)
					{			
						$relations = array();
						
						foreach($langs as $lang)
						{	
							$rels = $relationManager->getChilds($this, $field, $lang);
							
							$relations = array_merge($relations, $rels);
						}
						
						if(count($relations) > 0)
						{
							foreach($relations as $relation)
							{
                                if($recursive)
                                {
                                    $relation->deleteRecursive($lang);
                                    $relationManager->clearRelation($this, $relation, $field, $lang);
                                }
                            }
						}
					}
				}					
			}					
		}			
	}
	
	private function getAllRelationsLangs($relationType)
	{
		$langs = array();
		
		if(!is_null($relationType) && $relationType != "")
		{	
			$dbProvider = f_database_Provider::getInstance();
			
			$sql = "SELECT `lang` FROM `f_relation` WHERE `parent_id`=:docId OR `child_id`=:docId AND `relation_type`=:relationType";
			
			$langs = $dbProvider->setQuery($sql)->execute(array("docId" => $this->getUniqueId(), "relationType" => $relationType))->getQueryResults(PDO::FETCH_COLUMN);
		}
		
		return $langs;
	}
	
// =========================================== MANAGEMENT METHODS	
	
	/**
	 * Insert relations in db
	 *
	 */
	private function defineRelations()
	{
		$relationManager = f_relation_Manager::getInstance();
		
		$relationFields = $this->relationFields();

		if(isset($relationFields[0]) && $relationFields[0] != "")
		{
			foreach($relationFields as $relationField)
			{
				// Retrieve the method name to get related documents
				$method = "get".ucfirst($relationField)."Nb";

				// Retrieve related documemnts
				$relations = $this->$method();

				if(is_array($relations))
				{
					/* @var $relatedDocument f_document_BaseDocument */
					foreach($relations as $relatedDocument)
					{		
						// Set relation in db			
						$relationManager->setRelation($this, $relatedDocument, $relationField, null, $this->getLang());
					}
				}
				
				$dbProvider = f_database_Provider::getInstance();
		
				$sql = "SELECT COUNT(`child_id`) FROM `f_relation` WHERE `relation_type`=:relationType AND `lang`=:lang AND `parent_id`=:parentId";
				
				$nbRelations = $dbProvider->setQuery($sql)->execute(array("relationType" => 'testRelation', "lang" => $this->getLang(), "parentId" => $this->getUniqueId()))->getQueryResult(PDO::FETCH_COLUMN);
						
				$method = "set".ucfirst($relationField);
				$this->$method(intval($nbRelations));
			}
		}
	}
	
	/**
	 * Check if localized document is already in table
	 *
	 * @return boolean
	 */
	private function isI18nDocumentInTable()
	{
		$dbProvider = f_database_Provider::getInstance();
	
		$datas = $dbProvider->getLocalizedDatasById($this->getTableName(), $this->getUniqueId(), $this->getLang());
	
		return isset($datas[0]);
	}
	
	/**
	 * Get the document properties in an array for insert in db
	 *
	 * @return array 
	 */
	protected function getDocumentProperties()
	{
		$properties = (array)$this;

		$content = array();
		$relationFields = array();
		
		// if there is field for related documents
		if(method_exists($this, "relationFields"))
		{
			$relationFields = $this->relationFields();
		}
		
		// Exclude Fields
		$excludeFields = array("document_vo", "isRecursive");
		
		// Exclude Fields and relation Fields
		$allExcludeFields = array_merge($relationFields, $excludeFields);

		$validFields = $this->getFieldsBag();
		
		// properties check loop
		foreach($properties as $key => $value)
		{
			$keys = explode("\0", $key);
			$fieldName = end($keys);
			
			// clean up fields not to include in db
			if(!$this->in_array_nocase($fieldName, $allExcludeFields))
			{
				if($this->in_array_nocase($fieldName, $validFields))
				{
					$content[$fieldName] = $value;
				}
			}				
		}
		
		return $content;
	}
	
	/**
	 * Check if value is in array but all in lowercase
	 *
	 * @param string $search
	 * @param array $array
	 * @return boolean
	 */
	private function in_array_nocase($search, &$array) 
	{
		$search = strtolower($search);
		foreach ($array as $item)
		{
			if (strtolower($item) == $search)
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Return the last id inserted in database (f_document)
	 *
	 * @return integer
	 */
	private function getLastId()
	{
		$dbProvider = f_database_Provider::getInstance();
		
		return $dbProvider->getLastInsertedId();
	}

	/**
	 * Return classes hierarchy of the current object (For extented classes)
	 *
	 * @return array
	 */	
	private function getHierarchy()
	{
		$parents = class_parents($this);
		$classes = array(get_class($this));
		
		if(count($parents) > 0)
		{
			foreach($parents as $parent)
			{
				if(!preg_match("#^[-.a-z0-9A-Z_]+(Base|baseDocument)$#",$parent))
				{
					$classes[] = $parent;
				}
			}
		}
		return $classes;
	}
	
	/**
	 * Check if document is targetable or not
	 * @return boolean
	 */
	public function isTargetable()
	{
		$targets = f_target_Manager::getInstance()->setDocument($this)->getAvailableTargets();

		return count($targets) > 0;
	}
	
	/**
	 * Get current document related targets
	 * @return array
	 */
	public function getDocumentTargets()
	{
		return f_target_Manager::getTargetsByDocument($this);
	}
	
	/**
	 * Check if current document is targeted
	 * @return boolean
	 */
	public function isTargeted()
	{
		return f_target_Manager::isDocumentTargeted($this);
	}

	/**
	 * Check if current document has the given target setted
	 * @param string $targetName
	 * @return boolean
	 */
	public function hasTarget($targetName)
	{
	    $targets = $this->getDocumentTargets();
	    if(count($targets) > 0)
	    {
		foreach($targets as $target)
		{
		    if($target["label"] == $targetName)
		    {
                return true;
		    }
		}
	    }
	    return false;
	}

	/**
	 * Get current document parent
	 * @return f_document_BaseDocument
	 */
	public function getDocumentParent($model = null)
	{
        if(is_null($model))
        {
            return f_relation_Manager::getInstance()->getUniqueParent($this->getUniqueId(), $this->getLang());
        }
        else
        {
            $parents = f_relation_Manager::getInstance()->getParentsByDocType($this->getUniqueId(), $model, $this->getLang());

            if(is_array($parents))
            {
                return end($parents);
            }

            return null;
        }
	}
}